/**
 *  Copyright 2012 Rafal Korytkowski
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.googlecode.rapidjunit.launcher;

import java.lang.reflect.Field;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.internal.junit.model.ITestSessionListener;
import org.eclipse.jdt.internal.junit.model.TestCaseElement;
import org.eclipse.jdt.internal.junit.model.TestElement;
import org.eclipse.jdt.internal.junit.model.TestRunSession;
import org.eclipse.jdt.internal.junit.model.TestSuiteElement;
import org.eclipse.jdt.internal.junit.ui.TestRunnerViewPart;
import org.eclipse.jdt.junit.model.ITestElement;
import org.eclipse.ui.PlatformUI;

/**
 * Queues tests to be rerun.
 */
@SuppressWarnings("restriction")
public class TestsRerunQueue implements ITestSessionListener {
	
	private final static TestsRerunQueue instance = new TestsRerunQueue();
	
	private final Queue<TestElement> tests = new ConcurrentLinkedQueue<TestElement>();
	
	private TestRunSession currentSession;
	
	private TestRunnerViewPart runnerView;
	
	private TestsRerunQueue() {
	}
	
	public static TestsRerunQueue getInstance() {
		return instance;
	}
	
	public void stopCurrentSession() {
		if (currentSession != null) {
			currentSession.removeTestSessionListener(this);
			currentSession.stopTestRun();
		}
		
		tests.clear();
	}
	
	protected boolean isCurrentSessionAlive() {
		TestRunSession newSession = null;
		try {
			runnerView = (TestRunnerViewPart) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
			        .showView(TestRunnerViewPart.NAME);
			
			Field fTestRunSessionField = runnerView.getClass().getDeclaredField("fTestRunSession");
			fTestRunSessionField.setAccessible(true);
			
			Object object = fTestRunSessionField.get(runnerView);
			
			if (object == null) {
				return false;
			}
			
			newSession = (TestRunSession) object;
		}
		catch (Exception e) {
			throw new RuntimeException("RapidJunit is not compatible with your JUnit plugin", e);
		}
		
		if (newSession.isKeptAlive()) {
			if (!newSession.equals(currentSession)) {
				stopCurrentSession();
				currentSession = newSession;
				newSession.addTestSessionListener(this);
			}
			
			return true;
		} else {
			stopCurrentSession();
			newSession.removeTestSessionListener(this);
			return false;
		}
		
	}
	
	public boolean queueTest(IType test) {
		if (!isCurrentSessionAlive()) {
			return false;
		}
		
		boolean scheduled = false;
		
		ITestElement[] testSuites = currentSession.getChildren();
		for (int i = 0; i < testSuites.length; i++) {
			TestSuiteElement testSuite = (TestSuiteElement) testSuites[i];
			for (int j = 0; j < testSuite.getChildren().length; j++) {
				TestElement runningTest = (TestElement) testSuite.getChildren()[j];
				String testName = test.getFullyQualifiedName();
				if (runningTest.getTestName().contains(testName)) {
					tests.offer(runningTest);
					scheduled = true;
				}
			}
			
			if (scheduled) {
				break;
			}
		}
		
		if (!scheduled) {
			currentSession.stopTestRun();
		}
		
		return scheduled;
	}
	
	public void run() {
		runNextTest();
	}
	
	protected void runNextTest() {
		if (currentSession.isKeptAlive()) {
			if (!tests.isEmpty()) {
				TestElement test = tests.poll();
				String testName = test.getTestName().substring(0, test.getTestName().indexOf("("));
				runnerView.rerunTest(test.getId(), test.getClassName(), testName, "debug");
			}
		} else {
			currentSession.removeTestSessionListener(this);
			tests.clear();
		}
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#sessionStarted()
	 */
	@Override
	public void sessionStarted() {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#sessionEnded(long)
	 */
	@Override
	public void sessionEnded(long elapsedTime) {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#sessionStopped(long)
	 */
	@Override
	public void sessionStopped(long elapsedTime) {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#sessionTerminated()
	 */
	@Override
	public void sessionTerminated() {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#testAdded(org.eclipse.jdt.internal.junit.model.TestElement)
	 */
	@Override
	public void testAdded(TestElement testElement) {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#runningBegins()
	 */
	@Override
	public void runningBegins() {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#testStarted(org.eclipse.jdt.internal.junit.model.TestCaseElement)
	 */
	@Override
	public void testStarted(TestCaseElement testCaseElement) {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#testEnded(org.eclipse.jdt.internal.junit.model.TestCaseElement)
	 */
	@Override
	public void testEnded(TestCaseElement testCaseElement) {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#testFailed(org.eclipse.jdt.internal.junit.model.TestElement,
	 *      org.eclipse.jdt.internal.junit.model.TestElement.Status, java.lang.String,
	 *      java.lang.String, java.lang.String)
	 */
	@Override
	public void testFailed(TestElement testElement, org.eclipse.jdt.internal.junit.model.TestElement.Status status,
	                       String trace, String expected, String actual) {
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#testReran(org.eclipse.jdt.internal.junit.model.TestCaseElement,
	 *      org.eclipse.jdt.internal.junit.model.TestElement.Status, java.lang.String,
	 *      java.lang.String, java.lang.String)
	 */
	@Override
	public void testReran(TestCaseElement testCaseElement, org.eclipse.jdt.internal.junit.model.TestElement.Status status,
	                      String trace, String expectedResult, String actualResult) {
		run();
	}
	
	/**
	 * @see org.eclipse.jdt.internal.junit.model.ITestSessionListener#acceptsSwapToDisk()
	 */
	@Override
	public boolean acceptsSwapToDisk() {
		return false;
	}
	
}
